package apiexample.transform;

import java.sql.Connection;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import pku.sei.codesnippet.CodeSnippet;
import pku.sei.codesnippet.SnippetArgumentTypeInfo;
import pku.sei.codesnippet.SnippetFieldAccessInfo;
import pku.sei.codesnippet.SnippetMethodInvoInfo;
import util.StringDealer;
import apiexample.transform.datastructure.UsageExample;
import apiexample.transform.descriptionextraction.DescriptionExtraction;
import apiexample.transform.descriptionextraction.EnhancedDescriptionExtraction;
import apiexample.transform.util.ProgrammingEleConstructer;

import com.apiexample.server.dao.DescriptionDao;
import com.apiexample.server.dao.ExampleClassDao;
import com.apiexample.server.dao.ExampleDao;
import com.apiexample.server.dao.ExampleMethodDao;
import com.apiexample.server.entity.DescriptionEntity;
import com.apiexample.server.entity.ExampleClassRelation;
import com.apiexample.server.entity.ExampleEntity;
import com.apiexample.server.entity.ExampleMethodRelation;

import dao.SegmentDao;
import dao.entity.SegmentEntity;

public class TransformLogic {
	private static final int MAX_PRECEDING_DES_LENGTH = 150;
	private static final int MAX_FOLLOWING_DES_LENGTH = 100;
	private static final String DELIMITERS = " \t\n\r\f()[]{}',?<>\"";
	private static final String BEGINTAG = "<span class=\"highlightkeywords\">";
	private static final String ENDTAG = "</span>";

	private static final byte PRECEDING = 1;
	private static final byte FOLLOWING = -1;

	private static final int MIN_LOC = 2;

	private static final int MAX_LOC = 300;

	private Connection apiexample_conn = null;

	private Connection apiserver_conn = null;

	public TransformLogic(Connection apiexample_conn, Connection apiserver_conn) {
		this.apiexample_conn = apiexample_conn;
		this.apiserver_conn = apiserver_conn;
	}

	private ArrayList<SegmentEntity> segments = null;

	public ArrayList<UsageExample> getExamplesInOnePage(int pageID, String pageLink, int classID, String pageTitle) {
		ArrayList<UsageExample> examples = new ArrayList<UsageExample>();
		// get all segments from the page containing this code segment
		segments = SegmentDao.getSegmentListByPageID(apiexample_conn, pageID);
		for (int i = 0; i < segments.size(); ++i) {
			try {
				if (segments.get(i).getType() == SegmentEntity.CODE_SEGMENT) {
					String thisContent = segments.get(i).getContent();
					// trim the beginning empty line, and ending empty line.
					thisContent = StringDealer.trimEmptyLine(thisContent);
					int thisLineNum = StringDealer.getLineNumber(thisContent);
					if (thisLineNum > MIN_LOC && thisLineNum < MAX_LOC) {
						long time_1 = System.currentTimeMillis();
						// judge whether this code snippet can be parsed, if not,
						// continue; otherwise, generate a new usageExample
						CodeSnippet snippet = null;
						CodeSnippet snippetCanParse = null;
						try {
							snippet = new CodeSnippet(thisContent, false);// to avoid exception, put it in this try
																			// block
							if (snippet == null) {
								continue;
							}
							snippetCanParse = snippet.adjustToParsedSnippet(false);
						} catch (Exception e) {
							e.printStackTrace();
							continue;
						}

						if (snippetCanParse != null) {
							// can be parsed
							// record structure information
							// use the modified content as the code source
							UsageExample example = new UsageExample();
							ExampleEntity exampeEntity = new ExampleEntity(CentralDataPool.generateExampleID(), 0,
									classID, 0, 0, 0, 0, 0, 0, pageLink,
									snippetCanParse.getSrcContentAfterPreprocess(), segments.get(i).getBegin_tag(),
									segments.get(i).getEnd_tag(), pageTitle, "", examples.size() + 1, "");
							example.setEntity(exampeEntity);
							example.setSnippet(snippetCanParse);
							example.pageIndx = i;// for extracting descriptive texts

							// organize data
							examples.add(example);
						} else {
							// cannot be parsed, forceparse
							continue;
						}
						System.out.println("[INFO]Parse finished, time cost:" + (System.currentTimeMillis() - time_1));
					}
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return examples;
	}

	/**
	 * update example's structure information
	 * 
	 * @param usageExamples
	 *            void
	 */
	public ArrayList<UsageExample> updateUsageExample(ArrayList<UsageExample> usageExamples) {
		ArrayList<UsageExample> result = new ArrayList<UsageExample>();
		for (UsageExample usageExample : usageExamples) {
			// two sets to avoid duplicated information
			HashSet<Integer> methodIDSet = new HashSet<Integer>();
			HashSet<Integer> classIDSet = new HashSet<Integer>();
			// get all of the used APIs, methods by this example
			HashSet<SnippetMethodInvoInfo> methodInvoes = (HashSet<SnippetMethodInvoInfo>) usageExample.getSnippet()
					.getMethodInvoInfos();
			HashSet<SnippetFieldAccessInfo> fieldInvoes = (HashSet<SnippetFieldAccessInfo>) usageExample.getSnippet()
					.getFieldAccessInfos();
			if (methodInvoes.size() == 0 && fieldInvoes.size() == 0) {
				// there are no method invocations or field invocations in this example snippet, do not consider this
				// example as a valid example
				continue;
			}
			// Handle the method invocation information in the usage example
			processMethodInvocations(usageExample, methodInvoes, methodIDSet, classIDSet);
			// Handle the field accessment information in the usage example
			processFieldInvocations(usageExample, fieldInvoes, methodIDSet, classIDSet);
			// Handle other referenced classes, such as the class which are used only as parameters but its methods are
			// not invoked
			/*
			 * Graphics2D g = (Graphics2D) g1; *java.awt.Paint* shadowPaint = new Color(0, 0, 0, 100); // Translucent
			 * black g.setPaint(shadowPaint);
			 */
			processTypeRef(usageExample, usageExample.getSnippet().getInvolvedTypeFullNames(), usageExample
					.getSnippet().getParser().getRefClassNames(), classIDSet);

			// process the declared elements in the code snippet
			usageExample.generateDeclaredElements();
			// referenced elements information have been processed in the above steps

			// get other information: such as NCSS,CSS
			processOtherInfo(usageExample);

			// get its descriptions
			DescriptionExtraction descriptionExtraction = new EnhancedDescriptionExtraction();
			ArrayList<DescriptionEntity> preceding_Deses = descriptionExtraction.getPrecedingDescriptionsOfCode(
					segments, usageExample, usageExample.pageIndx, MAX_PRECEDING_DES_LENGTH);
			ArrayList<DescriptionEntity> following_Deses = descriptionExtraction.getFollowingDescriptionsOfCode(
					segments, usageExample, usageExample.pageIndx, MAX_FOLLOWING_DES_LENGTH);
			usageExample.setPrecedingDes(preceding_Deses);
			usageExample.setFollowingDes(following_Deses);

			result.add(usageExample);
		}

		return result;
	}

	/**
	 * Handle other referenced classes, such as the class which are used only as parameters but its methods are not
	 * invoked
	 * 
	 * @param usageExample
	 * @param refTypeFullNames
	 * @param refTypeSimpleNames
	 * @param classIDSet
	 */
	public void processTypeRef(UsageExample usageExample, Set<String> refTypeFullNames, Set<String> refTypeSimpleNames,
			HashSet<Integer> classIDSet) {

		for (String refTypeSimpeName : refTypeSimpleNames) {
			usageExample.referencedTypeNames.add(refTypeSimpeName);// full name
		}

		for (String refTypeName : refTypeFullNames) {

			usageExample.referencedTypeNames.add(refTypeName);// simple name

			Integer usedTypeID = CentralDataPool.classNameMapID.get(refTypeName);
			if (usedTypeID == null) {
				// can not find this method in the apiserver database
				System.out.println("[WARNING]can not find this used type in the apiserver database, name is:"
						+ refTypeName);
				continue;
			} else {
				// judge the relation type
				if (classIDSet.add(usedTypeID)) {
					if (usedTypeID != usageExample.getEntity().getClass_id()) {
						// this type is parsed
						ExampleClassRelation ec_relation = new ExampleClassRelation(usageExample.getEntity().getId(),
								usedTypeID, ExampleClassRelation.REL_TYPE_PARSER_GET);
						usageExample.exampleClassRelations.add(ec_relation);
						System.out.println("[Example-Class]" + refTypeName);
					} else {
						ExampleClassRelation ec_relation = new ExampleClassRelation(usageExample.getEntity().getId(),
								usedTypeID, ExampleClassRelation.REL_TYPE_DIRECT_GET);
						usageExample.exampleClassRelations.add(ec_relation);
						System.out.println("[Example-Class]" + refTypeName);
					}
				}
			}
		}
	}

	/**
	 * get other information: such as NCSS,CSS
	 * 
	 * @param usageExample
	 */
	public void processOtherInfo(UsageExample usageExample) {
		// Length of Comments
		// List<String> comments = usageExample.getSnippet().getComments();
		// int lengthOfComments = 0;
		// for (String comment : comments) {
		// lengthOfComments += StringDealer.countTokens(comment);
		// }
		int lineOfComments = usageExample.getSnippet().getCommentsLineCount();
		int lineOfCode = usageExample.getSnippet().getLineCountWithoutEmptyLine();

		usageExample.getEntity().setCss(lineOfComments);
		// Now, use LOC-lineOfComments as NCSS temporarily
		int NCSS = lineOfCode - lineOfComments;
		usageExample.getEntity().setNcss((NCSS < 0 ? 0 : NCSS));
		// Now ,use CCN to store the adjustments temporarily
		usageExample.getEntity().setCcn(usageExample.getSnippet().getPreprocessMethod());
		// Now, use lang_type to store number of unresolved type temporarily
		usageExample.getEntity().setLang_type(usageExample.getSnippet().getUnresolvedSimpleNames().size());
	}

	/**
	 * Handle the method invocation information in the usage example
	 * 
	 * @param example
	 * @param methodInvoes
	 */
	public void processMethodInvocations(UsageExample usageExample, HashSet<SnippetMethodInvoInfo> methodInvoes,
			HashSet<Integer> methodIDSet, HashSet<Integer> classIDSet) {
		Iterator<SnippetMethodInvoInfo> ite = methodInvoes.iterator();
		StringBuffer methodInvoLineInfo = new StringBuffer(usageExample.getEntity().getFeature());
		HashMap<Integer, String> methodInvoLineMap = new HashMap<Integer, String>();
		while (ite.hasNext()) {
			SnippetMethodInvoInfo methodInvo = ite.next();
			String usedTypeName = methodInvo.getDeclaredTypeName();
			// methodName format: className_methodName_inputtype

			// The parameters is null means the referenced type is probably wrongly guessed, therefore, skip it directly
			if (methodInvo.getMostLikelyParas() == null) {
				continue;
			}

			// The bracket around the tri-operator is necessary due to different priority of + and ==?!!!!
			String methodName = usedTypeName + "_" + methodInvo.getMethodName() + "_" + methodInvo.getMostLikelyParas();

			// add it into this example's referenced element names
			if (!methodInvo.getMethodName().equals("<init>")) {
				usageExample.referencedMethodNames.add(methodInvo.getMethodName());
			}

			// Record names of actual parameters of methods invoked by this example
			for (SnippetArgumentTypeInfo argInfo : methodInvo.getArguments()) {
				usageExample.actualArgs.addAll(argInfo.getArgValues());
			}

			Integer usedTypeID = CentralDataPool.classNameMapID.get(usedTypeName);
			if (usedTypeID == null) {
				// can not find this method in the apiserver database
				System.out.println("[WARNING]can not find this used type in the apiserver database, name is:"
						+ usedTypeName);
				continue;
			} else {
				// judge the relation type
				if (classIDSet.add(usedTypeID)) {
					if (usedTypeID != usageExample.getEntity().getClass_id()) {
						// this type is parsed
						ExampleClassRelation ec_relation = new ExampleClassRelation(usageExample.getEntity().getId(),
								usedTypeID, ExampleClassRelation.REL_TYPE_PARSER_GET);
						usageExample.exampleClassRelations.add(ec_relation);
						System.out.println("[Example-Class]" + usedTypeName);
					} else {
						ExampleClassRelation ec_relation = new ExampleClassRelation(usageExample.getEntity().getId(),
								usedTypeID, ExampleClassRelation.REL_TYPE_DIRECT_GET);
						usageExample.exampleClassRelations.add(ec_relation);
						System.out.println("[Example-Class]" + usedTypeName);
					}
				}
			}
			Integer methodID = CentralDataPool.methodNameMapID.get(methodName);
			if (methodID == null) {
				// can not find this method in the apiserver database
				System.out
						.println("[WARNING]can not find this method in the apiserver database, name is:" + methodName);
				continue;
			} else {
				// relation: example-method relation
				String methodInvoFeature_r = methodInvoLineMap.get(methodID);
				String newFeature = methodInvo.getLineNo() + ExampleMethodRelation.SEPERATOR_LINE_INDEX;
				if (methodInvoFeature_r == null) {
					methodInvoLineMap.put(methodID, newFeature);
				} else {
					if (methodInvoFeature_r.indexOf(newFeature) == -1) {
						methodInvoFeature_r += newFeature;
						methodInvoLineMap.remove(methodID);
						methodInvoLineMap.put(methodID, methodInvoFeature_r);
					}
				}
				// if (methodIDSet.add(methodID)) {
				// ExampleMethodRelation em_relation = new ExampleMethodRelation(
				// usageExample.getEntity().getId(), methodID
				// .intValue(), 0);
				// usageExample.exampleMethodRelations.add(em_relation);
				// System.out.println("[Example-Method]" + methodName);
				// }
				// method invocation feature of an example: methodID,lineNo-methodID,lineNo-methodID,lineNo-
				String methodInvoFeature = methodID + "," + methodInvo.getLineNo();
				if (methodInvoLineInfo.indexOf(methodInvoFeature) == -1) {
					methodInvoLineInfo.append(methodInvoFeature + "-");
				}
			}
		}
		// store example-method relation
		Iterator<Integer> temp_ite = methodInvoLineMap.keySet().iterator();
		while (temp_ite.hasNext()) {
			Integer methodInteger = temp_ite.next();
			String methodInvoFeature_r = methodInvoLineMap.get(methodInteger);
			ExampleMethodRelation em_relation = new ExampleMethodRelation(usageExample.getEntity().getId(),
					methodInteger.intValue(), 0, methodInvoFeature_r == null ? "" : methodInvoFeature_r);
			usageExample.exampleMethodRelations.add(em_relation);
			System.out.println("[Example-Method]" + methodInteger);
		}
		// set the method invocation line number feature
		usageExample.getEntity().setFeature(methodInvoLineInfo.toString());
	}

	/**
	 * Handle the field accessment information in the usage example
	 * 
	 * @param example
	 * @param fieldInvoes
	 */
	public void processFieldInvocations(UsageExample usageExample, HashSet<SnippetFieldAccessInfo> fieldInvoes,
			HashSet<Integer> methodIDSet, HashSet<Integer> classIDSet) {
		Iterator<SnippetFieldAccessInfo> ite = fieldInvoes.iterator();
		HashMap<Integer, String> fieldAccessLineMap = new HashMap<Integer, String>();
		StringBuffer fieldAccessLineInfo = new StringBuffer(usageExample.getEntity().getFeature());
		while (ite.hasNext()) {
			SnippetFieldAccessInfo fiedlInvo = ite.next();
			String usedTypeName = fiedlInvo.getDeclaredTypeName();

			// fieldName format: className-fieldName
			String fieldName = usedTypeName + "-" + fiedlInvo.getFieldName();
			Integer usedTypeID = CentralDataPool.classNameMapID.get(usedTypeName);
			if (usedTypeID == null) {
				// can not find this method in the apiserver database
				System.out.println("[WARNING]can not find this used type in the apiserver database, name is:"
						+ usedTypeName);
				continue;
			} else {
				// judge the relation type
				if (classIDSet.add(usedTypeID)) {
					if (usedTypeID != usageExample.getEntity().getClass_id()) {
						// this type is parsed
						ExampleClassRelation ec_relation = new ExampleClassRelation(usageExample.getEntity().getId(),
								usedTypeID, ExampleClassRelation.REL_TYPE_PARSER_GET);
						usageExample.exampleClassRelations.add(ec_relation);
						System.out.println("[Example-Class]" + usedTypeName);
					} else {
						ExampleClassRelation ec_relation = new ExampleClassRelation(usageExample.getEntity().getId(),
								usedTypeID, ExampleClassRelation.REL_TYPE_DIRECT_GET);
						usageExample.exampleClassRelations.add(ec_relation);
						System.out.println("[Example-Class]" + usedTypeName);
					}
				}
			}
			Integer fieldID = CentralDataPool.methodNameMapID.get(fieldName);
			if (fieldID == null) {
				// can not find this method in the apiserver database
				System.out.println("[WARNING]can not find this FIELD in the apiserver database, name is:" + fieldName);
				continue;
			} else {
				// relation: example-field relation
				String fieldAccessFeature_r = fieldAccessLineMap.get(fieldID);
				String newFeature = fiedlInvo.getLineNo() + ExampleMethodRelation.SEPERATOR_LINE_INDEX;
				if (fieldAccessFeature_r == null) {
					fieldAccessLineMap.put(fieldID, newFeature);
				} else {
					if (fieldAccessFeature_r.indexOf(newFeature) == -1) {
						fieldAccessFeature_r += newFeature;
						fieldAccessLineMap.remove(fieldID);
						fieldAccessLineMap.put(fieldID, fieldAccessFeature_r);
					}
				}
				// if (methodIDSet.add(fieldID)) {
				// //field调用目前没有行号信息
				// ExampleMethodRelation em_relation = new ExampleMethodRelation(
				// usageExample.getEntity().getId(), fieldID
				// .intValue(), 0,"");
				// usageExample.exampleMethodRelations.add(em_relation);
				// System.out.println("[Example-Field]" + fieldName);
				// }
				// method invocation feature of an example: methodID,lineNo-methodID,lineNo-methodID,lineNo-
				String fieldAccessFeature = fieldID + "," + fiedlInvo.getLineNo();
				if (fieldAccessLineInfo.indexOf(fieldAccessFeature) == -1) {
					fieldAccessLineInfo.append(fieldAccessFeature + "-");
				}
			}
		}

		Iterator<Integer> temp_ite = fieldAccessLineMap.keySet().iterator();
		while (temp_ite.hasNext()) {
			Integer fieldID = temp_ite.next();
			String fieldAccessFeature_r = fieldAccessLineMap.get(fieldID);
			ExampleMethodRelation em_relation = new ExampleMethodRelation(usageExample.getEntity().getId(), fieldID
					.intValue(), 0, fieldAccessFeature_r == null ? "" : fieldAccessFeature_r);
			usageExample.exampleMethodRelations.add(em_relation);
			System.out.println("[Example-Field]" + fieldID);
		}
		// set the method invocation line number feature
		usageExample.getEntity().setFeature(fieldAccessLineInfo.toString());
	}

	/**
	 * Store usage example information into apiserver database
	 * 
	 * @param usageExamples
	 *            void
	 */
	public void storeBack(ArrayList<UsageExample> usageExamples) {
		System.out.println("[INFO]Going to store back examples for this api");
		for (UsageExample example : usageExamples) {

			// insert example
			example.getEntity().setProgramming_ele(ProgrammingEleConstructer.consturctProgrammingEle(example));
			ExampleDao.insertExample(apiserver_conn, example.getEntity());

			// insert example-class
			for (ExampleClassRelation ec_relation : example.exampleClassRelations) {
				ExampleClassDao.insertExampleClassRelation(apiserver_conn, ec_relation);
			}
			// insert example-method
			for (ExampleMethodRelation em_relation : example.exampleMethodRelations) {
				ExampleMethodDao.insertExampleMethodRelation(apiserver_conn, em_relation);
			}
			// insert example-descriptions
			for (DescriptionEntity des : example.getPrecedingDes()) {
				des.setId(-1);
				des.setExample_id(example.getEntity().getId());
				DescriptionDao.insertDescription(apiserver_conn, des);
			}
			for (DescriptionEntity des : example.getFollowingDes()) {
				des.setId(-1);
				des.setExample_id(example.getEntity().getId());
				DescriptionDao.insertDescription(apiserver_conn, des);
			}
		}
		System.out.println("[INFO]Store back over");
	}

}
